﻿Imports System.Windows.Media.Animation
Imports System.Collections.ObjectModel
Imports System.Windows.Markup

<ContentProperty("ImageSources")> _
Public Class Lightbox
	Inherits Control

	Private WithEvents _CloseButton As Image = Nothing
	Private WithEvents _LeftColumn As Grid = Nothing
	Private WithEvents _RightColumn As Grid = Nothing

	Private _LeftGrid As Border = Nothing
	Private _RightGrid As Border = Nothing
	Private _CloseGrid As Border = Nothing
	Private _LayoutGrid As Grid = Nothing
	Private _CurrentImageBorder As Border = Nothing
	Private _CurrentImage As Image = Nothing

	Shared Sub New()
		'This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
		'This style is defined in Themes\Generic.xaml
		DefaultStyleKeyProperty.OverrideMetadata(GetType(Lightbox), New FrameworkPropertyMetadata(GetType(Lightbox)))
	End Sub

	Public Overrides Sub OnApplyTemplate()
		MyBase.OnApplyTemplate()
		UpdateControl()
		Me.Focus()
	End Sub

	Private Sub UpdateControl()
		If _CloseButton Is Nothing Then _CloseButton = TryCast(GetTemplateChild("CloseButton"), Image)
		If _LeftColumn Is Nothing Then _LeftColumn = TryCast(GetTemplateChild("LeftColumn"), Grid)
		If _RightColumn Is Nothing Then _RightColumn = TryCast(GetTemplateChild("RightColumn"), Grid)
		If _LeftGrid Is Nothing Then _LeftGrid = TryCast(GetTemplateChild("LeftGrid"), Border)
		If _RightGrid Is Nothing Then _RightGrid = TryCast(GetTemplateChild("RightGrid"), Border)
		If _CloseGrid Is Nothing Then _CloseGrid = TryCast(GetTemplateChild("closeGrid"), Border)
		If _LayoutGrid Is Nothing Then _LayoutGrid = TryCast(GetTemplateChild("LayoutGrid"), Grid)
		If _CurrentImageBorder Is Nothing Then _CurrentImageBorder = TryCast(GetTemplateChild("CurrentImageBorder"), Border)
		If _CurrentImage Is Nothing Then _CurrentImage = TryCast(GetTemplateChild("CurrentImage"), Image)
	End Sub

#Region " Dependency Properties "

#Region " ImageSourceProperty Dependency Property "

#Region " ImageSource Property "

	Public Property ImageSource() As ImageSource
		Get
			Return DirectCast(GetValue(ImageSourceProperty), ImageSource)
		End Get
		Set(ByVal value As ImageSource)
			' Do not set the dependency property if there is something data bound to the property as this will distroy the property binding.
			Dim bindingExpression As BindingExpressionBase = TryCast(ReadLocalValue(Lightbox.ImageSourceProperty), BindingExpressionBase)
			If bindingExpression Is Nothing Then SetValue(ImageSourceProperty, value)
		End Set
	End Property

#End Region

#Region " ImageSource Dependency Property "

	Public Shared ReadOnly ImageSourceProperty As DependencyProperty = DependencyProperty.Register("ImageSource", GetType(ImageSource), GetType(Lightbox), New PropertyMetadata(Nothing, AddressOf OnImageSourcePropertyChanged))

#End Region

#Region " ImageSource Property Changed CallBack "

	Private Shared Sub OnImageSourcePropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
		Dim source As Lightbox = TryCast(d, Lightbox)
		If source Is Nothing Then Throw New ArgumentException("source is not an instance of Lightbox!")
		source.OnImageSourceChanged(d, e)
	End Sub

#End Region

#Region " ImageSource Overridable Property Changed "

	Protected Overridable Sub OnImageSourceChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
		' Provide validation here. Developer may override this.
		RaiseImageSourceChangedEvent(d, e)
	End Sub

#End Region

#Region " Raise ImageSource Routed Event "

	Protected Overridable Sub RaiseImageSourceChangedEvent(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
		' Do nothing except raise the event here.  Developer may override this and decide not to raise the event.
		RaiseEvent ImageSourceChanged(d, e)
	End Sub

#End Region

#Region " ImageSource Routed Event "

	Public Event ImageSourceChanged As PropertyChangedCallback

#End Region

#End Region

#Region " CaptionProperty Dependency Property "

#Region " Caption Property "

	Public Property Caption() As String
		Get
			Return DirectCast(GetValue(CaptionProperty), String)
		End Get
		Set(ByVal value As String)
			' Do not set the dependency property if there is something data bound to the property as this will distroy the property binding.
			Dim bindingExpression As BindingExpressionBase = TryCast(ReadLocalValue(Lightbox.CaptionProperty), BindingExpressionBase)
			If bindingExpression Is Nothing Then SetValue(CaptionProperty, value)
		End Set
	End Property

#End Region

#Region " Caption Dependency Property "

	Public Shared ReadOnly CaptionProperty As DependencyProperty = DependencyProperty.Register("Caption", GetType(String), GetType(Lightbox), New PropertyMetadata(String.Empty, AddressOf OnCaptionPropertyChanged))

#End Region

#Region " Caption Property Changed CallBack "

	Private Shared Sub OnCaptionPropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
		Dim source As Lightbox = TryCast(d, Lightbox)
		If source Is Nothing Then Throw New ArgumentException("source is not an instance of Lightbox!")
		source.OnCaptionChanged(d, e)
	End Sub

#End Region

#Region " Caption Overridable Property Changed "

	Protected Overridable Sub OnCaptionChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
		' Provide validation here. Developer may override this.
		RaiseCaptionChangedEvent(d, e)
	End Sub

#End Region

#Region " Raise Caption Routed Event "

	Protected Overridable Sub RaiseCaptionChangedEvent(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
		' Do nothing except raise the event here.  Developer may override this and decide not to raise the event.
		RaiseEvent CaptionChanged(d, e)
	End Sub

#End Region

#Region " Caption Routed Event "

	Public Event CaptionChanged As PropertyChangedCallback

#End Region

#End Region

#Region " ImageSourcesProperty Dependency Property "

#Region " ImageSources Property "

	Public Property ImageSources() As ObservableCollection(Of LightboxImage)
		Get
			Return DirectCast(GetValue(ImageSourcesProperty), ObservableCollection(Of LightboxImage))
		End Get
		Set(ByVal value As ObservableCollection(Of LightboxImage))
			' Do not set the dependency property if there is something data bound to the property as this will distroy the property binding.
			Dim bindingExpression As BindingExpressionBase = TryCast(ReadLocalValue(Lightbox.ImageSourcesProperty), BindingExpressionBase)
			If bindingExpression Is Nothing Then SetValue(ImageSourcesProperty, value)
		End Set
	End Property

#End Region

#Region " ImageSources Dependency Property "

	Public Shared ReadOnly ImageSourcesProperty As DependencyProperty = DependencyProperty.Register("ImageSources", GetType(ObservableCollection(Of LightboxImage)), GetType(Lightbox), New PropertyMetadata(New ObservableCollection(Of LightboxImage), AddressOf OnImageSourcesPropertyChanged))

#End Region

#Region " ImageSources Property Changed CallBack "

	Private Shared Sub OnImageSourcesPropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
		Dim source As Lightbox = TryCast(d, Lightbox)
		If source Is Nothing Then Throw New ArgumentException("source is not an instance of Lightbox!")
		source.OnImageSourcesChanged(d, e)
	End Sub

#End Region

#Region " ImageSources Overridable Property Changed "

	Protected Overridable Sub OnImageSourcesChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
		' Provide validation here. Developer may override this.
		RaiseImageSourcesChangedEvent(d, e)
	End Sub

#End Region

#Region " Raise ImageSources Routed Event "

	Protected Overridable Sub RaiseImageSourcesChangedEvent(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
		' Do nothing except raise the event here.  Developer may override this and decide not to raise the event.
		RaiseEvent ImageSourcesChanged(d, e)
	End Sub

#End Region

#Region " ImageSources Routed Event "

	Public Event ImageSourcesChanged As PropertyChangedCallback

#End Region

#End Region

#Region " MaxImageHeightProperty Dependency Property "

#Region " MaxImageHeight Property "

	Public Property MaxImageHeight() As Double
		Get
			Return DirectCast(GetValue(MaxImageHeightProperty), Double)
		End Get
		Set(ByVal value As Double)
			' Do not set the dependency property if there is something data bound to the property as this will distroy the property binding.
			Dim bindingExpression As BindingExpressionBase = TryCast(ReadLocalValue(Lightbox.MaxImageHeightProperty), BindingExpressionBase)
			If bindingExpression Is Nothing Then SetValue(MaxImageHeightProperty, value)
		End Set
	End Property

#End Region

#Region " MaxImageHeight Dependency Property "

	Public Shared ReadOnly MaxImageHeightProperty As DependencyProperty = DependencyProperty.Register("MaxImageHeight", GetType(Double), GetType(Lightbox), New PropertyMetadata(Double.NaN, AddressOf OnMaxImageHeightPropertyChanged))

#End Region

#Region " MaxImageHeight Property Changed CallBack "

	Private Shared Sub OnMaxImageHeightPropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
		Dim source As Lightbox = TryCast(d, Lightbox)
		If source Is Nothing Then Throw New ArgumentException("source is not an instance of Lightbox!")
		source.OnMaxImageHeightChanged(d, e)
	End Sub

#End Region

#Region " MaxImageHeight Overridable Property Changed "

	Protected Overridable Sub OnMaxImageHeightChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
		' Provide validation here. Developer may override this.
		RaiseMaxImageHeightChangedEvent(d, e)
	End Sub

#End Region

#Region " Raise MaxImageHeight Routed Event "

	Protected Overridable Sub RaiseMaxImageHeightChangedEvent(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
		' Do nothing except raise the event here.  Developer may override this and decide not to raise the event.
		RaiseEvent MaxImageHeightChanged(d, e)
	End Sub

#End Region

#Region " MaxImageHeight Routed Event "

	Public Event MaxImageHeightChanged As PropertyChangedCallback

#End Region

#End Region

#Region " MaxImageWidthProperty Dependency Property "

#Region " MaxImageWidth Property "

	Public Property MaxImageWidth() As Double
		Get
			Return DirectCast(GetValue(MaxImageWidthProperty), Double)
		End Get
		Set(ByVal value As Double)
			' Do not set the dependency property if there is something data bound to the property as this will distroy the property binding.
			Dim bindingExpression As BindingExpressionBase = TryCast(ReadLocalValue(Lightbox.MaxImageWidthProperty), BindingExpressionBase)
			If bindingExpression Is Nothing Then SetValue(MaxImageWidthProperty, value)
		End Set
	End Property

#End Region

#Region " MaxImageWidth Dependency Property "

	Public Shared ReadOnly MaxImageWidthProperty As DependencyProperty = DependencyProperty.Register("MaxImageWidth", GetType(Double), GetType(Lightbox), New PropertyMetadata(Double.NaN, AddressOf OnMaxImageWidthPropertyChanged))

#End Region

#Region " MaxImageWidth Property Changed CallBack "

	Private Shared Sub OnMaxImageWidthPropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
		Dim source As Lightbox = TryCast(d, Lightbox)
		If source Is Nothing Then Throw New ArgumentException("source is not an instance of Lightbox!")
		source.OnMaxImageWidthChanged(d, e)
	End Sub

#End Region

#Region " MaxImageWidth Overridable Property Changed "

	Protected Overridable Sub OnMaxImageWidthChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
		' Provide validation here. Developer may override this.
		RaiseMaxImageWidthChangedEvent(d, e)
	End Sub

#End Region

#Region " Raise MaxImageWidth Routed Event "

	Protected Overridable Sub RaiseMaxImageWidthChangedEvent(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
		' Do nothing except raise the event here.  Developer may override this and decide not to raise the event.
		RaiseEvent MaxImageWidthChanged(d, e)
	End Sub

#End Region

#Region " MaxImageWidth Routed Event "

	Public Event MaxImageWidthChanged As PropertyChangedCallback

#End Region

#End Region

#End Region

#Region " Events "

	Private Sub _LeftColumn_MouseEnter(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles _LeftColumn.MouseEnter
		If _LeftGrid IsNot Nothing Then _LeftGrid.Visibility = Windows.Visibility.Visible
		Cursor = Cursors.Hand
	End Sub

	Private Sub _LeftColumn_MouseLeave(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles _LeftColumn.MouseLeave
		If _LeftGrid IsNot Nothing Then _LeftGrid.Visibility = Windows.Visibility.Hidden
		Cursor = Cursors.Arrow
	End Sub

	Private _leftColumnClicking As Boolean
	Private Sub _LeftColumn_MouseLeftButtonDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles _LeftColumn.MouseLeftButtonDown
		_leftColumnClicking = True
	End Sub

	Private Sub _LeftColumn_MouseLeftButtonUp(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles _LeftColumn.MouseLeftButtonUp
		If Not _leftColumnClicking Then Exit Sub
		SwitchImages(False)
		_leftColumnClicking = False
	End Sub

	Private Sub _RightColumn_MouseEnter(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles _RightColumn.MouseEnter
		If _RightGrid IsNot Nothing Then _RightGrid.Visibility = Windows.Visibility.Visible
		Cursor = Cursors.Hand
	End Sub

	Private Sub _RightColumn_MouseLeave(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles _RightColumn.MouseLeave
		If _RightGrid IsNot Nothing Then _RightGrid.Visibility = Windows.Visibility.Collapsed
		Cursor = Cursors.Arrow
	End Sub

	Private _rightColumnClicking As Boolean
	Private Sub _RightColumn_MouseLeftButtonDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles _RightColumn.MouseLeftButtonDown
		_rightColumnClicking = True
	End Sub

	Private Sub _RightColumn_MouseLeftButtonUp(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles _RightColumn.MouseLeftButtonUp
		If Not _rightColumnClicking Then Exit Sub
		SwitchImages(True)
		_rightColumnClicking = False
	End Sub

	Private _CloseButtonCLicking As Boolean

	Private Sub _CloseButton_MouseEnter(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles _CloseButton.MouseEnter
		Cursor = Cursors.Hand
	End Sub

	Private Sub _CloseButton_MouseLeave(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles _CloseButton.MouseLeave
		Cursor = Cursors.Arrow
	End Sub
	Private Sub _CloseButton_MouseLeftButtonDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles _CloseButton.MouseLeftButtonDown
		_CloseButtonCLicking = True
	End Sub

	Private Sub _CloseButton_MouseLeftButtonUp(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles _CloseButton.MouseLeftButtonUp
		If Not _CloseButtonCLicking Then Exit Sub
		If _CloseGrid IsNot Nothing Then
			_CloseGrid.Visibility = Windows.Visibility.Collapsed
			_CloseGrid.Height = 0
		End If

		Me.Visibility = Windows.Visibility.Collapsed
		_CloseButtonCLicking = False
	End Sub

	Private Sub Lightbox_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Input.KeyEventArgs) Handles Me.KeyUp
		If e.Key = Key.Left Then
			SwitchImages(False)
		ElseIf e.Key = Key.Right Then
			SwitchImages(True)
		ElseIf e.Key = Key.Escape Then
			Me.Visibility = Windows.Visibility.Collapsed
		ElseIf e.Key = Key.P Then
			SwitchImages(False)
		ElseIf e.Key = Key.N Then
			SwitchImages(True)
		End If
		Me.Focus()
	End Sub

#End Region

	Private _isFirstLoad As Boolean = True
	Public Sub ShowImages()
		Me.Visibility = Windows.Visibility.Visible
		UpdateLayout()
		OnApplyTemplate()
		If _CloseGrid IsNot Nothing Then _CloseGrid.Visibility = Windows.Visibility.Visible
		SwitchImages(True)
		_isFirstLoad = False
		Me.Focus()
	End Sub

	Private _CurrentIndex As Integer = -1

	Private Sub SwitchImages(ByVal forward As Boolean)
		Dim newImageSource As ImageSource = ImageSource
		'CurrentImageBorder
		If forward Then
			' Going forward in the list
			If Not _isFirstLoad Then
				If ImageSources.Count - 1 = _CurrentIndex Then
					' At the end... Loop?
					newImageSource = DirectCast(ImageSources.Item(0), LightboxImage).ImageSource
					_CurrentIndex = 0
				ElseIf ImageSources.Count - 1 > _CurrentIndex Then
					' Not to the end, add one to the index
					newImageSource = DirectCast(ImageSources.Item(_CurrentIndex + 1), LightboxImage).ImageSource
					_CurrentIndex += 1
				ElseIf ImageSources.Count - 1 < _CurrentIndex Then
					newImageSource = DirectCast(ImageSources.Item(ImageSources.Count), LightboxImage).ImageSource
					_CurrentIndex = ImageSources.Count
				End If
			End If

		Else
			' Going backward in the list
			If _CurrentIndex = 0 Then
				' At the start... Loop?
				newImageSource = DirectCast(ImageSources.Item(ImageSources.Count - 1), LightboxImage).ImageSource
				_CurrentIndex = ImageSources.Count - 1
			ElseIf _CurrentIndex > 0 Then
				' Not to the start, subtract one to the index
				newImageSource = DirectCast(ImageSources.Item(_CurrentIndex - 1), LightboxImage).ImageSource
				_CurrentIndex -= 1
			ElseIf _CurrentIndex < 0 Then
				newImageSource = DirectCast(ImageSources.Item(0), LightboxImage).ImageSource
				_CurrentIndex = 0
			End If

		End If
		If _CloseGrid IsNot Nothing Then
			' Make the grid 0, then animate opening back up.
			_CloseGrid.Visibility = Windows.Visibility.Collapsed
			_CloseGrid.Visibility = Windows.Visibility.Visible
		End If

		If _CurrentImage IsNot Nothing Then
			' Make the image fade in again when changing pic.
			_CurrentImage.Visibility = Windows.Visibility.Collapsed
			_CurrentImage.Visibility = Windows.Visibility.Visible
		End If

		Dim img As New Image
		img.Source = newImageSource
		img.Measure(New Size(If(Double.IsNaN(MaxImageWidth), _LayoutGrid.ActualWidth, MaxImageWidth), If(Double.IsNaN(MaxImageHeight), _LayoutGrid.ActualHeight, MaxImageHeight)))

		' We have 3 seconds to finish this animation
		Dim imageHeightAnimation As New DoubleAnimation()
		imageHeightAnimation.From = _CurrentImage.ActualHeight
		imageHeightAnimation.To = img.DesiredSize.Height
		imageHeightAnimation.Duration = New Duration(New TimeSpan(0, 0, 0, 0, 500))


		Storyboard.SetTargetName(imageHeightAnimation, _CurrentImageBorder.Name)
		Storyboard.SetTargetProperty(imageHeightAnimation, New PropertyPath(FrameworkElement.HeightProperty))


		Dim imageWidthAnimation As New DoubleAnimation()
		imageWidthAnimation.From = _CurrentImage.ActualWidth
		imageWidthAnimation.To = img.DesiredSize.Width
		imageWidthAnimation.Duration = New Duration(New TimeSpan(0, 0, 0, 0, 500))


		Storyboard.SetTargetName(imageWidthAnimation, _CurrentImageBorder.Name)
		Storyboard.SetTargetProperty(imageWidthAnimation, New PropertyPath(FrameworkElement.WidthProperty))

		Dim sb As New Storyboard()
		sb.Children.Add(imageHeightAnimation)
		sb.Children.Add(imageWidthAnimation)
		sb.Begin(_CurrentImageBorder)

		ImageSource = newImageSource
		Caption = ImageSources.Item(_CurrentIndex).Caption

	End Sub

	Private Sub Lightbox_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
		If ImageSources.Count > 0 AndAlso _CurrentIndex = -1 Then
			_CurrentIndex = 0
			ImageSource = DirectCast(ImageSources.Item(_CurrentIndex), LightboxImage).ImageSource
		End If

	End Sub

End Class
